from __future__ import annotations
import datetime
from typing import Any

from django.db import models
from django.db.models import F
from django.db.models.signals import pre_save

from tg_bot.config import PayType

RU_STATUS = 'Статус'
RU_NAME = 'Название'
RU_DESCRIPTION = 'Описание'
MONEY_RU = '₽'


def now_datetime() -> datetime.datetime:
    return datetime.datetime.now()


class CustomQuerySet(models.QuerySet):
    def sum(self, value: str):
        return self.aggregate(_total=models.Sum(value))['_total']


class CustomManager(models.Manager):
    _queryset_class = CustomQuerySet


class BaseModel(models.Model):
    """
    Модель от которой наследуемся. Каждая модель будет иметь поля:
        1. Дата создания.
        2. Дата изменения.
    """
    date_create = models.DateTimeField(verbose_name='Дата создания', auto_now_add=True)
    date_update = models.DateTimeField(verbose_name='Дата изменения', auto_now=True)

    objects = CustomManager()

    def save(self, comment: str | None = 'Изменено администрацией.',
             **kwargs: Any):
        self.save_comment = comment
        super().save(**kwargs)

    class Meta:
        abstract = True


class TgUser(BaseModel):
    """Базовая модель пользователя телеграм"""
    BANNED = 'banned'
    GUEST = 'guest'
    REGULAR_GUEST = 'regular_guest'
    MAJOR = 'major'
    BUM = 'bum'
    ADMIN = 'admin'
    STATUS_CHOICES = [
        (BANNED, 'Заблокированный пользователь'),
        (GUEST, 'Гость'),
        (REGULAR_GUEST, 'Постоянный гость'),
        (ADMIN, 'Администратор'),
        (MAJOR, 'Мажор'),
        (BUM, 'Бомж'),
    ]

    user_id = models.BigIntegerField(primary_key=True, verbose_name='ID телеграм')
    username = models.CharField(max_length=128,
                                null=True,
                                default=None,
                                blank=True,
                                verbose_name='Никнейм')
    name = models.CharField(max_length=128, verbose_name='Имя')
    phone_number = models.CharField(max_length=128, verbose_name='Номер телефона')
    invite_by = models.ForeignKey('TgUser', null=True, default=None, on_delete=models.SET_NULL,
                                  related_name='referrals', verbose_name='Приглашен', blank=True)
    balance = models.FloatField(default=0.0, verbose_name='Баланс')
    warns = models.IntegerField(default=0, verbose_name='Предупреждения')
    total_sum = models.FloatField(default=0.0, verbose_name='Общие траты')
    status = models.CharField(max_length=64, choices=STATUS_CHOICES,
                              default=GUEST, verbose_name=RU_STATUS)

    def get_balance(self):
        return round(self.balance, 2)

    def get_total_sum(self):
        return round(self.total_sum, 2)

    @staticmethod
    def get_field_names() -> list[str]:
        """
        Получаем список атрибутов класса.
        :return: Список атрибутов класса.
        """
        return [field.name for field in TgUser._meta.fields]

    @staticmethod
    def pre_save(instance: TgUser, **kwargs: Any):
        if not instance._state.adding:
            previous: TgUser = TgUser.objects.get(pk=instance.pk)
            for field in TgUser.get_field_names():
                if (old_value := getattr(previous, field, None)) != (new_value := getattr(instance, field, None)):
                    change_type = field if field in LogTgUser.ChangeType.values else LogTgUser.ChangeType.ANOTHER
                    LogTgUser.objects.create(
                        user=instance,
                        change_type=change_type,
                        changed_from=old_value,
                        changed_to=new_value,
                        comment=instance.save_comment
                    )

    @classmethod
    def create_main_admin(cls, user: TgUser) -> bool:
        """Создаем супер админа."""
        if cls.objects.filter(status=cls.ADMIN).count() == 0:
            user.status = TgUser.ADMIN
            user.save()
            return True
        return False

    @classmethod
    def get_admins(cls, user_id_only: bool = False) -> list[int] | CustomQuerySet[TgUser]:
        """
        Получаем список администраторов.

        ----
        :param user_id_only: Если True, то возвращаем список из user_id.
        :return: list[user_id: int] | CustomQuerySet[TgUser]
        """
        if user_id_only:
            return [user for user in cls.objects.values_list('user_id', flat=True).filter(
                status=cls.ADMIN
            )]
        return cls.objects.filter(status=cls.ADMIN)

    def is_admin(self) -> bool:
        """
        Является ли пользователь админом бота.

        ----
        :return: Bool.
        """
        if self.status == self.ADMIN:
            return True
        return False

    def add_balance_for_referral(self) -> None:
        """
        Пополнение баланса за регистрацию рефералов.
        :return: None
        """
        config = Config.objects.first()
        self.balance += config.ref_bonus
        self.save('Бонус за приглашенного друга.')

    def add_balance_for_registration(self) -> None:
        """
        Пополнение баланса за регистрацию по чужой реферальной ссылке.
        :return: None
        """
        config = Config.objects.first()
        self.balance += config.invite_bonus
        self.save('Бонус за регистрацию.')

    def add_cashback(self, amount: float) -> None:
        self.balance += amount
        self.save('Начисление бонусов.')

    def add_total_sum(self, amount: int | float) -> None:
        self.total_sum += amount
        self.save('Увеличение общих трат.')

    def set_invite_by(self, invite_by: int | str) -> bool:
        """
        Устанавливаем реферальную ссылку юзеру.

        :param invite_by: Айди пользователя.
        :return: True or False.
        """
        if not self.invite_by and invite_by:
            invite_by: TgUser = TgUser.objects.filter(user_id=int(invite_by)).first()
            if invite_by and invite_by != self:
                self.invite_by = invite_by
                self.save('Регистрация по реферальной ссылке.')
                if Config.objects.first():
                    self.add_balance_for_registration()
                    invite_by.add_balance_for_referral()
                return True
        return False

    @classmethod
    def register(cls,
                 user_id: str | int,
                 username: str | None,
                 name: str,
                 phone_number: str,
                 deep_link: str | int | None = None):
        if cls.objects.filter(phone_number=phone_number).count() > 0:
            return None
        user = cls.objects.create(
            user_id=user_id,
            username=username,
            name=name,
            phone_number=phone_number
        )
        if config := Config.objects.first():
            user.balance += config.registration_bonus
            user.save('Начисление бонусов за регистрацию.')
        user.set_invite_by(deep_link)
        return user

    def personal_account_text(self):
        next_cashback = CashBack.objects.filter(amount__gt=self.total_sum).first()
        next_cashback_text = ''
        if next_cashback:
            next_cashback_text = f'💰 До следующего уровня кешбека нужно потратить: ' \
                                 f'<b>{(next_cashback.amount - self.total_sum):,}руб.</b>'
        return f'<b>Личный кабинет:</b>\n\n' \
               f'🆔 ID: <b><code>{self.user_id}</code></b>\n\n' \
               f'📱 Номер телефона: <b>{self.phone_number}</b>\n\n' \
               f'🪙 Баланс: <b>{self.get_balance()}руб.</b>\n\n' \
               f'👤 {RU_STATUS}: <b>{self.get_status_display()}</b>\n\n' \
               f'{next_cashback_text}'

    def get_name(self):
        return f'{self.name} | {self.user_id}'

    def __str__(self):
        return f'{self.name} | {self.user_id}'

    class Meta:
        verbose_name = 'Пользователь telegram'
        verbose_name_plural = 'Пользователи telegram'


class CashBack(BaseModel):
    """Отношение затрат к кэшбеку"""
    amount = models.FloatField(verbose_name='Сумма для кешбека')
    discount = models.FloatField(verbose_name='Размер кешбека')

    def __str__(self):
        return f'Кешбек от {self.amount}руб.'

    class Meta:
        verbose_name = 'Кэшбек'
        verbose_name_plural = 'Кэшбек'


class Action(BaseModel):
    """Описаний акций заведения"""
    name = models.CharField(max_length=255,
                            verbose_name=RU_NAME)
    description = models.TextField(null=True, blank=True, default=None,
                                   verbose_name=RU_DESCRIPTION)

    class Meta:
        verbose_name = 'Акция заведения'
        verbose_name_plural = 'Акции заведения'


class Config(BaseModel):
    """Конфигурация бота"""
    bot_id = models.BigIntegerField(primary_key=True, verbose_name='Айди бота')
    bot_url = models.CharField(max_length=128, verbose_name='Ссылка на бота')
    url = models.CharField(max_length=255, default=None, null=True,
                           verbose_name='Доп ссылка на бота', blank=True)
    registration_bonus = models.IntegerField(default=0, verbose_name='Бонус за регистрацию')
    invite_bonus = models.FloatField(default=0.0, verbose_name='Бонус по приглашению')
    ref_bonus = models.FloatField(default=0.0, verbose_name='Бонус за рефералов')
    warns = models.IntegerField(default=5, verbose_name='Кол-во предупреждений')
    sum_for_account_activation = models.FloatField(default=1000.0, verbose_name='Сумма для активации аккаунта')

    def __str__(self):
        return f'{self.bot_url} | {self.bot_id}'

    class Meta:
        verbose_name = 'Конфигурация бота'
        verbose_name_plural = 'Конфигурация ботов'


class Seat(BaseModel):
    """Посадки в заведении"""
    name = models.CharField(max_length=128,
                            verbose_name=RU_NAME)
    _max_count = models.IntegerField(default=2,
                                     verbose_name='Максимальная вместимость')
    status = models.BooleanField(default=True,
                                 verbose_name=RU_STATUS)
    join = models.OneToOneField('Seat', on_delete=models.SET_NULL, null=True, default=None,
                                blank=True,
                                verbose_name='Объединение')
    photo = models.TextField(null=True, default=None, blank=True,
                             verbose_name='Айди Фото')

    def get_active_sales_main(self):
        return self.sales_main.filter(end_date__isnull=True).order_by('-pk').first()

    def open_seat(self):
        """
        Сажаем гостей.

        :return: SalesMain object.
        """
        self.status = False
        self.save()
        return SalesMain.objects.create(
            seat=self
        )

    def close_seat(self):
        self.status = True
        self.save()
        for joined_seat in Seat.objects.filter(join=self):
            joined_seat.status = True
            joined_seat.join = None
            joined_seat.save()
        sales_main: SalesMain = self.sales_main.order_by('-start_date').first()
        if sales_main.sales.count() > 0:
            sales_main.end_date = datetime.datetime.now()
            sales_main.save()
        else:
            sales_main.delete()

    def join_with(self, other: 'Seat'):
        self.join = other
        self.status = False
        self.save()

    def un_join_seat(self):
        self.join = None
        self.status = True
        self.save()

    def get_booking(self):
        now_date = datetime.datetime.now() - datetime.timedelta(minutes=30)
        return self.booking.filter(datetime_book__gt=now_date)

    @classmethod
    def get_free(cls):
        return cls.objects.filter(status=True).order_by('pk')

    @classmethod
    def get_busy(cls):
        return cls.objects.filter(status=False, join__isnull=True).order_by('pk')

    @classmethod
    def get_joined(cls):
        return cls.objects.filter(status=False, join__isnull=False).order_by('pk')

    @property
    def max_count(self):
        return self._max_count

    @max_count.setter
    def max_count(self, max_count: int):
        if isinstance(max_count, int) and max_count > 0:
            self._max_count = max_count
        else:
            raise ValueError("Значение максимальной посадки может принимать только целые положительные значения!")

    def get_name(self):
        text = f'{RU_NAME}: <b>{self.name}</b>\n' \
               f'Максимальная вместимость: <b>{self.max_count}</b>\n' \
               f'В данный момент: <b>{"СВОБОДЕН" if self.status else "ЗАНЯТ"}</b>\n'
        return text

    def __str__(self):
        return f'{RU_NAME}: {self.name} | {self.max_count}'

    class Meta:
        verbose_name = 'Посадки в заведении'
        verbose_name_plural = 'Посадки в заведении'


class Booking(BaseModel):
    """Бронирование мест"""
    seat = models.ForeignKey(Seat, on_delete=models.CASCADE, related_name='booking')
    user = models.ForeignKey(TgUser, on_delete=models.CASCADE, related_name='booking')
    count_of_users = models.IntegerField(default=1)
    datetime_book = models.DateTimeField()

    class Meta:
        verbose_name = 'Бронирование мест'


class Category(BaseModel):
    """Категории товаров"""
    name = models.CharField(max_length=128,
                            verbose_name=RU_NAME)
    description = models.TextField(null=True, default=None, blank=True,
                                   verbose_name=RU_DESCRIPTION)
    is_visible = models.BooleanField(default=True,
                                     verbose_name='Отображение')
    is_active = models.BooleanField(default=True, verbose_name='Статус')

    def get_name(self):
        desc = f'\n\n<i>{self.description}</i>' if self.description else ' <b>отсутствует</b>'
        visible = '<b>вкл</b>' if self.is_visible else '<b>выкл</b>'
        text = f'{RU_NAME} категории: <b>{self.name}</b>\n' \
               f'Отображение пользователям: {visible}\n' \
               f'{RU_DESCRIPTION}:{desc}\n'
        return text

    @classmethod
    def check_and_update_category_status(cls):
        cls.objects.filter(
            is_active=True,
            products__is_visible=False
        ).distinct().exclude(
            pk__in=cls.objects.values('pk').filter(products__is_visible=True).distinct()
        ).update(is_active=False)
        cls.objects.filter(
            is_active=False,
            products__is_visible=True
        ).distinct().update(is_active=True)

    def __str__(self):
        return f'{self.name}'

    class Meta:
        verbose_name = 'Категории товаров'
        verbose_name_plural = 'Категории товаров'


class Product(BaseModel):
    """Продукты"""
    category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='products',
                                 verbose_name='Категория')
    name = models.CharField(max_length=128,
                            verbose_name=RU_NAME)
    description = models.TextField(null=True, default=None, blank=True,
                                   verbose_name=RU_DESCRIPTION)
    help = models.TextField(null=True, default=None, blank=True,
                            verbose_name='Технологичка')
    price = models.FloatField(default=0.0, verbose_name='Цена')
    photo = models.TextField(null=True, default=None, blank=True,
                             verbose_name='Айди фото')
    is_visible = models.BooleanField(default=True,
                                     verbose_name='Отображение')
    is_active = models.BooleanField(default=True, verbose_name='Статус')

    def get_name(self):
        text = f'Категория: {self.category.name}\n' \
               f'{RU_NAME}: {self.name}\n' \
               f'Цена: {self.price} руб.\n' \
               f'Отображение пользователям: {"вкл" if self.is_visible else "выкл"}\n' \
               f'{f"{RU_DESCRIPTION}: {self.description}" if self.description else ""}' \
               f'\n{f"Технологичка: {self.help}" if self.help else ""}'
        if self.compounds.all().count() > 0:
            text += '\n\nРасходники: '
            for compound in self.compounds.all():
                text += f'\n{compound.consumable.name} - {compound.count}{compound.consumable.unit}'
        return text

    def product_info_text(self) -> str:
        """
        Описание продукта для вывода пользователю.

        :return: Текст продукта.
        """
        return (f'<b>{self.name}</b>\n'
                f'<b>{self.description or ""}</b>\n'
                f'Цена: <b>{self.price:,} {MONEY_RU}</b>')

    @classmethod
    def check_and_update_products_status(cls):
        cls.objects.filter(
            compounds__consumable__total__gte=F('compounds__count'),
            is_active=False
        ).update(is_active=True)
        cls.objects.filter(
            compounds__consumable__total__lt=F('compounds__count'),
            is_active=True
        ).update(is_active=False)
        Category.check_and_update_category_status()

    def __str__(self):
        return f'{self.name} | {self.price}'

    class Meta:
        verbose_name = 'Продукт'
        verbose_name_plural = 'Продукты'


class Consumable(BaseModel):
    """Расходники"""
    name = models.CharField(max_length=255, verbose_name=RU_NAME)
    price = models.FloatField(default=0.0, verbose_name='Цена')
    unit = models.CharField(max_length=50, default='мл', verbose_name='Мера измерения')
    total = models.FloatField(default=0.0, verbose_name='Общее кол-во')

    def get_name(self):
        text = f'{RU_NAME}: <b>{self.name}</b>\n' \
               f'Цена за 1{self.unit} - <b>{self.price}руб</b>\n' \
               f'Всего - <b>{self.total}{self.unit}</b>'
        return text

    def __str__(self):
        return f'{self.name}'

    class Meta:
        verbose_name = 'Расходник'
        verbose_name_plural = 'Расходники'


class Compound(BaseModel):
    """Что и сколько входит в продукт для продажи"""
    product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='compounds',
                                verbose_name='Продукт')
    consumable = models.ForeignKey(Consumable, on_delete=models.CASCADE, related_name='compounds',
                                   verbose_name='Расходник')
    count = models.FloatField(verbose_name='Кол-во')

    def __str__(self):
        return f'{self.product} <-{self.consumable}'

    class Meta:
        verbose_name = 'Составляющие продукта'
        verbose_name_plural = 'Составляющие продуктов'


class Team(BaseModel):
    """Члены команды"""
    name = models.CharField(max_length=128,
                            verbose_name='Имя')
    is_active = models.BooleanField(default=True,
                                    verbose_name='Активен ли')

    def get_sales_count(self) -> int:
        return self.sales.count()

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = 'Член команды'
        verbose_name_plural = 'Члены команды'


class SalesMain(BaseModel):
    """Главная таблица продаж"""
    seat = models.ForeignKey(
        Seat,
        on_delete=models.CASCADE,
        related_name='sales_main',
        verbose_name='Стол'
    )
    start_date = models.DateTimeField(
        default=now_datetime,
        verbose_name='Время посадки'
    )
    end_date = models.DateTimeField(
        null=True,
        default=None,
        blank=True,
        verbose_name='Время ухода'
    )
    comment = models.TextField(
        null=True,
        default=None,
        blank=True,
        verbose_name='Комментарий'
    )

    @classmethod
    def get_day_main_sales(cls):
        now_date = datetime.datetime.today()
        if now_date.hour >= 15:
            check_date = now_date.replace(hour=15, minute=0, second=0, microsecond=0)
        else:
            check_date = (now_date - datetime.timedelta(days=1)).replace(hour=15, minute=0, second=0, microsecond=0)
        return cls.objects.filter(start_date__gte=check_date, start_date__lte=now_date)

    @classmethod
    def get_sales_main_by_date(cls, start_date: datetime.datetime,
                               end_date: datetime.datetime):
        return cls.objects.filter(
            start_date__gte=start_date,
            start_date__lte=end_date
        )

    def add_cashback(self) -> list[tuple[TgUser, float]]:
        return_data = []
        sales = self.sales.filter(user__isnull=False)
        paid_data = sales.values('user_id').annotate(
            paid_sum=models.Sum('cash') + models.Sum('terminal') + models.Sum('sber')
        )
        for i in paid_data:
            user: TgUser = TgUser.objects.get(user_id=i.get('user_id'))
            paid_sum: float = i.get('paid_sum')
            user.add_total_sum(paid_sum)
            cashback_percent = CashBack.objects.filter(amount__lte=user.total_sum).order_by('-amount').first()
            if not cashback_percent:
                continue
            cashback_sum = round(paid_sum / 100 * cashback_percent.discount, 2)
            user.add_cashback(cashback_sum)
            return_data.append(tuple((user, cashback_sum)))
        return return_data

    def get_total_sum(self):
        return self.sales.all().aggregate(price=models.Sum('price'))['price'] or 0

    def add_sale(self, team: Team, product: Product, user: TgUser = None):
        """
        Добавляем новую продажу.

        :param team: ORM-модель команды.
        :param product: ORM-модель продукта.
        :param user: ORM_модель пользователя.
        :return: None
        """
        Sales.objects.create(
            team=team,
            product=product,
            price=product.price,
            user=user,
            sales_main=self
        )
        for compound in product.compounds.all():
            compound.consumable.total -= compound.count
            compound.consumable.save()

    def move_seat(self, new_seat: Seat):
        for joined_seat in Seat.objects.filter(join=self.seat):
            joined_seat.un_join_seat()
        self.seat.status = True
        self.seat.save()
        new_seat.status = False
        new_seat.save()
        self.seat = new_seat
        self.save()

    def get_paid_sum(self):
        return self.get_cash_sum() + self.get_sber_sum() + self.get_terminal_sum()

    def get_cash_sum(self):
        return self.sales.all().aggregate(
            cash=models.Sum('cash')
        ).get('cash', 0) or 0

    def get_sber_sum(self):
        return self.sales.all().aggregate(
            sber=models.Sum('sber')
        )['sber'] or 0

    def get_terminal_sum(self):
        return self.sales.all().aggregate(
            terminal=models.Sum('terminal')
        )['terminal'] or 0

    def get_discount_sum(self):
        return self.sales.all().aggregate(
            discount=models.Sum('discount')
        )['discount'] or 0

    def get_rest_sum(self):
        if (rest_sum := self.get_total_sum() - self.get_paid_sum() - self.get_discount_sum()) >= 0:
            return rest_sum
        return 0

    def get_not_paid_sales(self):
        sales = self.sales.all()
        for sale in sales:
            if sale.get_rest_sum() == 0:
                sales = sales.exclude(pk=sale.pk)
        return sales

    def get_paid_sum_by_product(self, product: Product):
        data = self.sales.filter(
            product=product
        ).aggregate(
            paid_sum=models.Sum('cash') + models.Sum('sber') + models.Sum('terminal')
        )
        return data.get('paid_sum', 0) or 0

    def get_discount_sum_by_product(self, product: Product):
        data = self.sales.filter(
            product=product
        ).aggregate(
            discount=models.Sum('discount')
        )
        return data.get('discount', 0) or 0

    def get_total_sum_by_product(self, product: Product):
        data = self.sales.filter(
            product=product
        ).aggregate(
            total_sum=models.Sum('product__price')
        )
        return data.get('total_sum', 0) or 0

    def get_rest_sum_by_product(self, product: Product):
        return (self.get_total_sum_by_product(product) -
                self.get_discount_sum_by_product(product) -
                self.get_paid_sum_by_product(product))

    def get_text(self):
        text = f'{RU_NAME}: {self.seat.name}\n\n' \
               f'Начало стола: {self.date_create.strftime("%H:%M")}\n' \
               f'Примерный конец: {(self.date_create + datetime.timedelta(hours=2)).strftime("%H:%M")}\n\n' \
               f'Общий чек: {self.get_total_sum()}\n' \
               f'Осталось оплатить: {self.get_rest_sum()}\n' \
               f'Скидка: {self.get_discount_sum()}'
        return text

    def get_text_by_product(self, product: Product):
        sales = self.sales.filter(product=product)
        text = f'{RU_NAME}: {product.name}\n' \
               f'Цена за 1-шт {product.name} - {product.price}\n' \
               f'Всего: {sales.count()}\n\n' \
               f'Общая стоимость: {self.get_total_sum_by_product(product)}\n' \
               f'Скидка: {self.get_discount_sum_by_product(product)}\n' \
               f'Осталось оплатить: {self.get_rest_sum_by_product(product)}'
        return text

    class Meta:
        verbose_name = 'Главные продажи'


class SalesQuerySet(CustomQuerySet):
    def names_with_count(self):
        return self.values_list('product__name').annotate(count=models.Count('product__name'))

    def user_names(self):
        return self.values_list('user__name', flat=True).annotate(count=models.Count('user__name'))

    def team_names(self):
        return self.values_list('team__name', flat=True).annotate(count=models.Count('team__name'))

    def get_rest_sum(self):
        return self.sum('price') - self.sum('discount') - self.sum('cash') - self.sum('sber') - self.sum('terminal')

    def set_discount(self, discount_sum: int | float):
        for sale in self:
            sale: Sales
            discount_sum = sale.set_discount(discount_sum)
        return discount_sum

    def delete(self):
        for sale in self:
            sale.delete()

    def pay(self,
            amount: int | float = 0,
            pay_type: str = 'cash'):
        for sale in self:
            amount = sale.pay(amount, pay_type)
        return amount

    def get_text(self):
        names = ''
        team = ''
        users = ''
        for name, count in self.names_with_count():
            names += f'{name}-{count}, '
        price = self.sum('price')
        for team_name in self.team_names():
            team += f'{team_name}, ' if team_name else 'Не указано, '
        for user_name in self.user_names():
            users += f'{user_name}, ' if user_name else 'Не указано, '
        discount = self.sum('discount')
        rest_sum = self.get_rest_sum()
        text = f'Названия: {names}\n' \
               f'Цена: {price}\n' \
               f'Заказ делали: {team}\n' \
               f'Кешбек получит: {users}\n' \
               f'Скидка: {discount}\n' \
               f'Осталось оплатить: {rest_sum}'
        return text


class SalesManager(models.Manager):
    _queryset_class = SalesQuerySet


class Sales(BaseModel):
    """Продажи"""
    sales_main = models.ForeignKey(
        SalesMain,
        on_delete=models.CASCADE,
        related_name='sales',
        verbose_name='Главные продажи'
    )
    user = models.ForeignKey(
        TgUser,
        on_delete=models.CASCADE,
        related_name='sales',
        null=True,
        default=None,
        blank=True,
        verbose_name='Пользователь'
    )
    team = models.ForeignKey(
        Team,
        on_delete=models.CASCADE,
        related_name='sales',
        verbose_name='Член команды'
    )
    product = models.ForeignKey(
        Product,
        on_delete=models.CASCADE,
        related_name='sales',
        verbose_name='Товар'
    )
    price = models.FloatField(
        default=0.0,
        verbose_name='Цена',
        blank=True
    )
    cash = models.FloatField(
        default=0.0,
        verbose_name='Наличные'
    )
    sber = models.FloatField(
        default=0.0,
        verbose_name='Перевод'
    )
    terminal = models.FloatField(
        default=0.0,
        verbose_name='Терминал'
    )
    discount = models.FloatField(
        default=0.0,
        verbose_name='Скидка'
    )
    comment = models.TextField(
        null=True,
        default=None,
        blank=True,
        verbose_name='Комментарий'
    )

    objects = SalesManager()

    def get_paid_sum(self):
        paid_sum = self.cash + self.sber + self.terminal
        return paid_sum

    def get_rest_sum(self):
        if (rest_sum := self.price - self.get_paid_sum() - self.discount) >= 0:
            return rest_sum
        return 0

    def get_text(self):
        text = f'{RU_NAME}: <b>{self.product.name}</b>\n' \
               f'Цена: <b>{self.product.price}</b>\n\n' \
               f'Заказ делал: <b>{self.team.name}</b>\n' \
               f'Кешбек получит: <b>{self.user.name if self.user else "не указано..."}</b>\n\n' \
               f'Скидка: <b>{self.discount}</b>\n' \
               f'Осталось оплатить: <b>{self.get_rest_sum()}</b>'
        return text

    def delete(self, using: Any = None, keep_parents: bool = False):
        for compound in self.product.compounds.all():
            compound.consumable.total += compound.count
            compound.consumable.save()
        super(Sales, self).delete(using=using, keep_parents=keep_parents)

    def pay(self,
            amount: int | float = 0,
            pay_type: str = 'cash') -> int | float:
        rest_sum = self.get_rest_sum()
        if rest_sum == 0:
            return amount
        if rest_sum >= amount:
            pay_sum = amount
            amount = 0
        else:
            pay_sum = rest_sum
            amount -= rest_sum
        match pay_type:
            case 'cash':
                self.cash += pay_sum
            case 'sber':
                self.sber += pay_sum
            case 'terminal':
                self.terminal += pay_sum
            case _:
                raise ValueError
        self.save()
        return amount

    def set_discount(self, amount: float | int = 0) -> float | int:
        rest_sum = self.get_rest_sum()
        if rest_sum == 0:
            return amount
        if rest_sum >= amount:
            self.discount += amount
            amount = 0
        else:
            self.discount += rest_sum
            amount -= rest_sum
        self.save()
        return amount

    @classmethod
    def get_category_from_sales_main(cls, sales_main: SalesMain.objects):
        sales = cls.objects.filter(
            sales_main__in=sales_main
        )
        category_list = sales.values('product__category').annotate(count=models.Count(
            'product__category'
        )).order_by('-count')
        data = []
        for item in category_list:
            category = Category.objects.filter(pk=item['product__category']).first()
            category_sales: SalesQuerySet = sales.filter(product__category_id=category.pk)
            pay_data = PayType(
                cash=category_sales.sum('cash'),
                sber=category_sales.sum('sber'),
                terminal=category_sales.sum('terminal'),
                discount=category_sales.sum('discount'),
                rest=category_sales.get_rest_sum()
            )
            count = item["count"]
            data.append([category, count, pay_data])
        return data

    @classmethod
    def get_products_from_sales_main(cls, sales_main: SalesMain.objects):
        sales = cls.objects.filter(
            sales_main__in=sales_main
        )
        product_list = sales.values('product').annotate(count=models.Count(
            'product'
        )).order_by('-count')
        data = []
        for item in product_list:
            product = Product.objects.filter(pk=item['product']).first()
            count = item["count"]
            product_sales = sales.filter(product=product.pk).aggregate(
                cash=models.Sum('cash'),
                sber=models.Sum('sber'),
                terminal=models.Sum('terminal'),
                discount=models.Sum('discount'),
            )
            pay_data = PayType(
                cash=product_sales['cash'],
                sber=product_sales['sber'],
                terminal=product_sales['terminal'],
                discount=product_sales['discount'],
                rest=0
            )
            data.append([product, count, pay_data])
        return data

    def save(self, comment: str | None = 'Изменено администрацией.',
             **kwargs: Any):
        if self.price is None:
            self.price = self.product.price
        super().save(comment, **kwargs)

    class Meta:
        verbose_name = 'Продажи'


class CashBox(BaseModel):
    """Наличка в заведении"""
    nominal = models.IntegerField()
    count = models.IntegerField(default=0)

    class Meta:
        verbose_name = 'Наличка в заведении'


class Bonus(BaseModel):
    """Бонусы и скидки на продукты"""
    promo = models.CharField(max_length=128,
                             verbose_name='Промокод')
    product_id = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='bonus',
                                   verbose_name='Продукт')
    count_left = models.IntegerField(default=0,
                                     verbose_name='Осталось кол-во использований')
    discount = models.FloatField(default=0.0,
                                 verbose_name='Скидка')
    start_date = models.DateTimeField(auto_now_add=True,
                                      verbose_name='Начало действия')
    end_date = models.DateTimeField(null=True, default=None,
                                    verbose_name='Конец действия')

    def __str__(self):
        return f'Промокод№{self.pk} - {self.promo}'

    class Meta:
        verbose_name = 'Промокод'
        verbose_name_plural = 'Промокоды'


class BonusHistory(BaseModel):
    """История активации бонусов"""
    bonus = models.ForeignKey(Bonus, on_delete=models.CASCADE, related_name='history',
                              verbose_name='Промокод')
    user = models.ForeignKey(TgUser, on_delete=models.CASCADE, related_name='bonus_history',
                             verbose_name='Пользователь')

    class Meta:
        verbose_name = 'История активации промокодов'
        verbose_name_plural = 'История активации промокодов'


class Voucher(BaseModel):
    """Ваучеры"""
    voucher = models.CharField(max_length=128,
                               verbose_name='Ваучер')
    voucher_sum = models.FloatField(default=0.0,
                                    verbose_name='Сумма ваучера')
    count_left = models.IntegerField(default=0,
                                     verbose_name='Осталось кол-во использований')
    start_date = models.DateTimeField(auto_now_add=True,
                                      verbose_name='Начало использования')
    end_date = models.DateTimeField(null=True, default=None,
                                    verbose_name='Конец использования')

    def is_able_to_activate(self, user: TgUser) -> tuple[bool, str]:
        if self.history.filter(user=user).first():
            return False, 'Вы уже активировали этот ваучер!'
        else:
            if self.count_left > 0:
                if self.end_date > datetime.datetime.now():
                    return True, 'Приятной активации!)'
                else:
                    return False, 'Ваучер устарел!'
            else:
                return False, 'Превыше число использований ваучера!'

    def activate(self, user):
        self.count_left -= 1
        self.save()
        VoucherHistory.objects.create(
            voucher=self,
            user=user
        )

    def __str__(self):
        return f'Ваучер№{self.pk} - {self.voucher}'

    class Meta:
        verbose_name = 'Ваучер'
        verbose_name_plural = 'Ваучеры'


class VoucherHistory(BaseModel):
    """История активации ваучеров"""
    voucher = models.ForeignKey(Voucher, on_delete=models.CASCADE, related_name='history',
                                verbose_name='Ваучер')
    user = models.ForeignKey(TgUser, on_delete=models.CASCADE, related_name='voucher_history',
                             verbose_name='Пользователь')

    class Meta:
        verbose_name = 'История активации ваучеров'
        verbose_name_plural = 'История активации ваучеров'


class LogTgUser(BaseModel):
    """
    Логи изменения данных пользователя.
    """

    class ChangeType(models.TextChoices):
        USERNAME = 'username', 'UserName'
        NAME = 'name', 'Имя'
        INVITE_BY = 'invite_by', 'Реферальная ссылка'
        BALANCE = 'balance', 'Баланс'
        WARNS = 'warns', 'Предупреждения'
        STATUS = 'status', RU_STATUS
        ANOTHER = 'another', 'Прочее'

    user = models.ForeignKey(TgUser, on_delete=models.CASCADE, related_name='logs',
                             verbose_name='Пользователь')
    change_type = models.CharField(max_length=64, choices=ChangeType.choices,
                                   verbose_name='Тип изменения',
                                   default=ChangeType.ANOTHER)
    changed_from = models.CharField(max_length=128, null=True, blank=True,
                                    verbose_name='Старое значение')
    changed_to = models.CharField(max_length=128, null=True, blank=True,
                                  verbose_name='Новое значение')
    comment = models.TextField(null=True, blank=True, default=None,
                               verbose_name='Комментарий')

    def __str__(self):
        return f'{self.user} - {self.get_change_type_display()}'

    class Meta:
        verbose_name = 'Лог пользователя'
        verbose_name_plural = 'Логи пользователя'


class WorkShift(models.Model):
    datetime_open = models.DateTimeField(auto_now_add=True, verbose_name='Дата открытия смены')
    datetime_close = models.DateTimeField(null=True, default=None, verbose_name='Дата закрытия смены')
    team = models.ForeignKey(Team, on_delete=models.CASCADE, related_name='shifts', verbose_name='Член команды')

    @classmethod
    def open_shift(cls, team: Team) -> tuple[bool, WorkShift]:
        if shift := cls.objects.filter(team=team, datetime_close__isnull=True).first():
            return False, shift
        return True, cls.objects.create(team=team)

    @classmethod
    def close_shift(cls, team: Team) -> bool:
        if shift := cls.objects.filter(team=team, datetime_close__isnull=True).first():
            shift.datetime_close = datetime.datetime.now()
            shift.save()
            return True
        return False

    @classmethod
    def get_active_team_worker(cls) -> Team | None:
        work_shift = cls.objects.filter(datetime_close__isnull=True).first()
        if work_shift:
            return work_shift.team
        return None


pre_save.connect(TgUser.pre_save, TgUser)
